package the_Crash;

import java.util.ArrayList;
import java.util.List;

import repast.simphony.engine.schedule.ScheduleParameters;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.query.space.grid.GridCell;
import repast.simphony.query.space.grid.GridCellNgh;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.SpatialMath;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.continuous.NdPoint;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridPoint;

//The car abstract class is implemented by the DumbCar and SmartCar
public abstract class Car extends BaseSimEntity {

	private static long totalCars=0;
	private static List<Car> cars;
	protected SourceSink destination;
	protected SourceSink source;
	protected long tickCount;
	protected double speed;
	protected double maxSpeed;
	protected Direction currentHeading;
	protected Direction futureHeading; //direction to go at next intersection
	protected double accel;
	protected double deccel;
	protected Signal nextLight;
	protected TravelDirection nextTurn;//whether the car is going to turn left or right or go straight at the next intersection
	protected boolean done = false; //whether the car has either arrived at its destination or been in an accident
	protected double redColor;
	protected double blueColor;
	protected double greenColor;
	protected int ticksAtLight;
	protected int ticksStopped = 0;
	protected boolean isInitialized = false;
	protected boolean didTurn;
	protected boolean didChangeLanes;
	
	public static void globalReset() {
		cars = new ArrayList<Car>();
		totalCars = 0;
	}
	
	public Car(SourceSink destination, SourceSink source,double maxSpeed, double accel, double deccel,
			GridPoint location, Grid<Object> grid, ContinuousSpace<Object> space) {
		super(location, grid, space);
		this.destination = destination;
		this.source = source;
		this.tickCount = 0;
		this.ticksAtLight =0;
		this.maxSpeed = maxSpeed;
		this.accel = accel;
		this.deccel = deccel;		
		
		redColor=RandomHelper.nextDoubleFromTo(0, 10);
		blueColor=RandomHelper.nextDoubleFromTo(0,10);
		greenColor=RandomHelper.nextDoubleFromTo(0,10);
		
		totalCars++;
	}
	
	
	
	
	//***********************************************************************************************************
	//turns a car into an accident
	public static void createAccident(Car car, GridPoint location){
		CityBuilder.globalCarCount--;
		NdPoint point = CityBuilder.getSpace().getLocation(car);
		CityBuilder.addToContext(new Accident(point, location, CityBuilder.getGrid(),CityBuilder.getSpace()));
	}
	
	//*******************************************************************************************************
	
	abstract protected void receiveMessage(Message m);
	
	//*********************************************************************************************************
	//Check if the car is in an accident or has arrived at the beginning of each tick
	@ScheduledMethod (start = 1, interval = 1, priority = ScheduleParameters.FIRST_PRIORITY)
	public void checkSurroundings(){
		if(checkForAccident()){
			Car.createAccident(this, grid.getLocation(this));
			CityBuilder.removeFromContext(this);
			done = true;
			return;
		}
		if(checkForArrival()){
			this.destination.removeCar(this);
			done = true;
			return;
		}
		
	}
	
	//every tick the car will adjust its speed and move accordingly
	@ScheduledMethod (start = 1, interval = 1)
	public void step()
	{
		if(!done){
			changeSpeed();
			ticksAtLight++;
			this.nextLight.watchCar(ticksAtLight, this.futureHeading, this.nextTurn, this.currentHeading);
			this.move();
			tickCount++;
		}
	}
	
	
	/*
	 * This method is needed because the car must be added to the context first, then moved to its location, and only then can 
	 * its headings and values be initialized
	 */
	public void initialize(){
		this.moveToLocation();
		initializeCurrentHeading();
		findNextLight();
		planFutureHeading();
		isInitialized = true;
	}
	
	/*
	 * When the car is first placed down it determines its initial heading based on the heading of the street cell it first
	 * goes out on
	 */
	private void initializeCurrentHeading()
	{
		GridCellNgh<StreetCell> nghCreator = new GridCellNgh<>(grid, location, StreetCell.class, 0,0);
		List<GridCell<StreetCell>> gridCells = nghCreator.getNeighborhood(true);
		for(StreetCell cell: gridCells.get(0).items())
			currentHeading = cell.getHeading();
	}
	
	/*
	 * Selects the heading the vehicle will go at the next intersection
	 * 
	 */
	protected void planFutureHeading() {
		List<Direction> possibleDirections = this.getPossibleDirections();
		//randomly choose which direction the car will travel at the next light to move closer to its destination
		futureHeading = possibleDirections.get(RandomHelper.nextIntFromTo(0, possibleDirections.size()-1));
		nextTurn = currentHeading.getTurn(futureHeading);
	}
	
	//****************************************************************************************
	/*
	 * Adjusts the speed of the car depending on whether it needs to slow down or stop or if it can speed up
	 */
	protected void changeSpeed(){
		boolean didChangeLanes = false;
		boolean hasOpenPath = this.isPathOpen(Math.ceil(maxSpeed*2));
		double totalBraking = 0.0;//the amount (from 1 to 0) the car "pushes" on the brakes
		
		if(!hasOpenPath){
			//the car will attempt to change lanes if its path is blocked to avoid the other car or accident
			didChangeLanes = changeLanes();
			if(didChangeLanes){
				//now that the car has changed lanes it needs to make sure its new path is open
				hasOpenPath = this.isPathOpen(Math.ceil(maxSpeed*2));
			}
		}
		
		if(!hasOpenPath){
			totalBraking = 1.0;//slam on the brakes
		}
		
		if(!this.isIntersectionClear()){
			totalBraking = 1.0;//slam on the brakes
		}
		totalBraking += this.getAmountToStopForLight();
		
		if(totalBraking > 0){
			if(totalBraking > 1){//make sure you're not pushing the brakes more than 100%
				totalBraking = 1;
			}
			this.brake(totalBraking);
		}
		else{//the path is open and the car is free to accelerate
			this.accelerate(1);
		}
	}
	
	/*********************************************************************************
	/*
	 * Determines and carries out the car's movements
	 *
	 */
	protected void move(){
		if(this.speed == 0){
			if(this.ticksStopped >10){//try to find another way to get to its destination
				//because the car's path might be blocked
				this.planFutureHeading();
			}
			this.ticksStopped++;
			return;
		} else{
			ticksStopped = 0;
		}
		
		GridPoint currentLocation = grid.getLocation(this);
		StreetCell currentCell = this.getCurrentCell(currentLocation);
		if(currentCell == null){ //the car has left the city
			CityBuilder.leftCity(this);
			return;
		}
		this.findNextLight();
		boolean didTurn = false;
		//determine if the car needs to turn and determine if it is able to make that turn
		if(this.nextTurn.equals(TravelDirection.Left) && currentCell.CanTurnLeft && this.nextLight.isOpen(futureHeading, nextTurn, currentHeading)&& this.speed >0){//make the turn if the car is not stopped
			didTurn = this.makeLeftTurn();
		} else if(this.nextTurn.equals(TravelDirection.Right) && currentCell.CanTurnRight && this.nextLight.isOpen(futureHeading, nextTurn, currentHeading) && this.speed >0){
			didTurn = this.makeRightTurn();
		}
		
		if(didTurn){ 
			//update heading only when your heading changes (i.e. you turn)
			this.currentHeading = this.futureHeading;
			this.findNextLight();
			this.planFutureHeading();
			return;
		}
		
		//determine whether the car needs to move into the left or right lane
		boolean needToGoLeft = false;
		boolean needToGoRight = false;
		if(this.nextTurn.equals(TravelDirection.Left) &&!currentCell.CanTurnLeft){
			needToGoLeft = true;
		} else if((this.nextTurn.equals(TravelDirection.Right) &&!currentCell.CanTurnRight)||
				this.needToMoveRightToArrive()){
			needToGoRight = true;
		}
		
		boolean didChangeLanes = false;
		boolean didMove = false;
		if(!(needToGoRight || needToGoLeft)){ // simply move forward if you don't need to change lanes
			this.moveStraight();
			didMove = true;
		} else if(needToGoRight){
			didMove = this.moveToRightLane(currentCell);
			didChangeLanes = didMove;
		} else{
			didMove = this.moveToTurnLeft(currentCell);
			didChangeLanes = didMove;
		}
		
		if(!didMove){//then just move straight ahead
			this.moveStraight();
		}
		this.didTurn = didTurn;
		this.didChangeLanes = didChangeLanes;
		//check to make sure the car has not left the city
		currentLocation = grid.getLocation(this);
		if(CityBuilder.isInContext(this)){
			currentCell = this.getCurrentCell(currentLocation);
			if(currentCell == null){
				CityBuilder.leftCity(this);
				return;
			}
		}
	}
	
	//**************************************************************************************************
	//looks for the upcoming traffic light
	protected void findNextLight()
	{
		GridPoint currentLocation = grid.getLocation(this); //this was giving an error because the light was not at a location initially
		GridPoint currentLightLocation = null;
		if(this.nextLight != null){//this is to handle when the car is getting initialized and it does not have a next light yet
			currentLightLocation = grid.getLocation(this.nextLight);
		}
		//this calculates where the next light will be
		double x = -1;
		double y = -1;
		double spacing = (ParameterResolver.getBlockSize()+ (CityBuilder.lanesPerStreet * CityBuilder.laneWidth));
		if(currentHeading.equals(Direction.East)){
			x =  Math.ceil(currentLocation.getX()/spacing)*spacing + CityBuilder.lanesOnSide;
			y = Math.round(currentLocation.getY()/spacing)*spacing + CityBuilder.lanesOnSide;
		} else if(currentHeading.equals(Direction.West)){
			x =  Math.floor(currentLocation.getX()/spacing)*spacing + CityBuilder.lanesOnSide;
			y = Math.round(currentLocation.getY()/spacing)*spacing + CityBuilder.lanesOnSide;
		}
		 else if(currentHeading.equals(Direction.North)){
			x =  Math.round(currentLocation.getX()/spacing)*spacing + CityBuilder.lanesOnSide;
			y = Math.ceil(currentLocation.getY()/spacing)*spacing + CityBuilder.lanesOnSide;
		}
		 else if(currentHeading.equals(Direction.South)){
			x =  Math.round(currentLocation.getX()/spacing)*spacing + CityBuilder.lanesOnSide;
			y = Math.floor(currentLocation.getY()/spacing)*spacing + CityBuilder.lanesOnSide;
		}
		
		//make the adjustment if the car calculates its next light to be off the grid
		if(x > grid.getDimensions().getWidth()){
			x = currentLightLocation.getX();
		}
		if(y>grid.getDimensions().getHeight()){
			y = currentLightLocation.getY();
		}
		
		//check to see if the car has found a new light
		if(currentLightLocation !=null && (int)x==currentLightLocation.getX() && (int)y==currentLightLocation.getY()){
			//then we do nothing
			return;
		} else {
			ticksAtLight = 0;
			nextLight = null;
			for(Object o : grid.getObjectsAt((int) (x),(int) (y))){
				if(o instanceof Signal){
					nextLight = (Signal) o;
				}
			}
			//now that we have calculated a new light we need to change our future planned heading
			this.planFutureHeading();
		}
	}
	
	//****************************************************************************************************
		/*
		 * Gets a list of possible directions that the car can go to move closer to its destination
		 */
		protected List<Direction> getPossibleDirections(){
			GridPoint destLocation = destination.location;
			int x = destLocation.getX() - nextLight.location.getX();
			int y = destLocation.getY() - nextLight.location.getY();
			//determine which direction to go to reach the destination based on the location of the next light
			//by choosing which directions will move the car closer to its destination among possible direction
			ArrayList<Direction> possibleDirections = new ArrayList<Direction>();
			if(!(currentHeading.equals(Direction.South) || y<0))
				possibleDirections.add(Direction.North);
			if(!(currentHeading.equals(Direction.North) || y>0))
				possibleDirections.add(Direction.South);
			if(!(currentHeading.equals(Direction.East) || x>0))
				possibleDirections.add(Direction.West);
			if(!(currentHeading.equals(Direction.West) || x<0 ))
				possibleDirections.add(Direction.East);
			
			return possibleDirections;
		}
	
	//apply the brakes to decelerate 
	public void brake(double amount)
	{
		speed = speed - amount*deccel;
		if(speed<0)
			speed=0;
	}
	
	//hit the gas to accelerate 
	public void accelerate(double amount)
	{
		speed = speed + amount*accel;
		if(speed>maxSpeed)
			speed=maxSpeed;
	}
	
	//****************************************************************************
	//check if the car has collided
	protected boolean checkForAccident(){
		GridPoint currentLocation = grid.getLocation(this);
		int numCars = 0;
		if(currentLocation.equals(nextLight.location)){
			return false;
		}
		//the car will always find itself at its own grid cell, thus it needs to see whether
		//there are more than one car at its current gridcell to see if there is an accident
		for(Object o : grid.getObjectsAt(currentLocation.getX(),currentLocation.getY())){
			if(o instanceof Car || o instanceof Accident){
				if(space.getDistance(space.getLocation(this), space.getLocation(o))==0 ){
					numCars++;
				}
			}
		}
		return numCars >1;
	}
	
	//***********************************************************************************************
	/*
	 * Checks directly in front of the car to see if the path is not blocked by another car or accident
	 */
	protected boolean isPathOpen(double distanceToCheck){
		GridPoint currentLocation = grid.getLocation(this);
		int[] unitVelocity = this.getUnitVelocityFromHeading(this.currentHeading);
		Car c;
		//here the car loops through to check the grid cells directly in front of it for other cars or accidents in its path
		for(int i=1; i<=distanceToCheck; i++){
			if(CityBuilder.withinGrid(currentLocation.getX()+unitVelocity[0]*i, currentLocation.getY()+unitVelocity[1]*i))
				for(Object o : grid.getObjectsAt(currentLocation.getX()+unitVelocity[0]*i, currentLocation.getY()+unitVelocity[1]*i)){
					//checking for accidents or cars at that location
					if(o instanceof Car || o instanceof Accident){
						if(o instanceof Accident){//always stop
							return false;
						} else {
							c = (Car) o;
							//only need to stop for cars if we are going to overtake them
							//the long condition in parentheses is for dealing with when two left turning cars are facing each other in the intersection
							if(c.speed <= this.speed + this.accel && !(c.nextTurn.equals(TravelDirection.Left)&& this.nextTurn.equals(TravelDirection.Left)&&
									c.currentHeading.equals(this.currentHeading.getOppsiteHeading()) && 
									this.getDistanceToNextLight()<= CityBuilder.lanesOnSide + CityBuilder.laneWidth)){
								return false;
							}
						}
					}
				}
		}
		return true;
	}
	
	//*****************************************************************************************
	/*
	 * The car will attempt to change lanes in the case that its path is blocked. Returns true if the move was successful
	 */
	protected boolean changeLanes(){
		boolean didChangeLanes = false;
		StreetCell currentCell = this.getCurrentCell(grid.getLocation(this));
		//only change lanes if you are heading straight through the next intersection
		if((this.nextTurn.equals(TravelDirection.Straight) || speed ==0) && currentCell!= null){
			if(currentCell.CanTurnRight){//it is currently in the right lane
				didChangeLanes = this.moveToLeftLane(currentCell);
			} else if(!currentCell.CanTurnLeft) {//it is in the left lane
				didChangeLanes = this.moveToRightLane(currentCell);
			}
		}
		return didChangeLanes;
	}
	
	//*****************************************************************************
	/*
	 * Checks to see if it needs to stop for the upcoming light, returns amount to press on brake if it needs to stop
	 * otherwise it returns 0 if you don't need to stop for the light
	 */
	protected double getAmountToStopForLight(){
		//now we need to check if the next light is telling us to stop
		//we only need to check if it is within the distance the car can move
		//we also need to make sure the car is not already within the intersection
		double totalBraking = 0.0;
		double distanceToLight = this.getDistanceToNextLight();
		if (distanceToLight >= (CityBuilder.lanesOnSide*CityBuilder.laneWidth + CityBuilder.laneWidth) && distanceToLight <= (this.speed*1.5 + CityBuilder.lanesOnSide*CityBuilder.laneWidth + CityBuilder.laneWidth)){
			if(!nextLight.isOpen(this.futureHeading,this.nextTurn,this.currentHeading)){
				totalBraking = 1;//slam on the brakes
			}
			//here the car will slow down if the light it is approaching is about to change, in otherwords, the light is "yellow"
		} else if(distanceToLight > (CityBuilder.lanesOnSide*CityBuilder.laneWidth) && distanceToLight <= this.speed*5+ CityBuilder.lanesOnSide*CityBuilder.laneWidth + CityBuilder.laneWidth&&
				this.nextLight.isAboutToChange()){
			totalBraking +=0.5;
		}
		return totalBraking;
	}
	
	//****************************************************************************************************
	/*
	 * Here the car will check its path through the intersection to make sure that it will not get stuck in the middle
	 * of the intersection
	 */
	protected boolean isIntersectionClear(){
		//first check to see if the car is at the intersection
		if(this.getDistanceToNextLight() <= CityBuilder.lanesOnSide + 2*CityBuilder.laneWidth){
			//now we need to check to see if the intersection is clear, we check to the end of the intersection
			return this.isPathOpen(this.getDistanceToNextLight()+CityBuilder.lanesOnSide);//CityBuilder.lanesPerStreet*CityBuilder.laneWidth + 2*CityBuilder.laneWidth);
		}
		//otherwise it doesn't matter what the intersection is like
		return true;
	}
	
	//*********************************************************************************
	/*
	 * returns true if the car actually changed lanes, false if otherwise
	 */
	protected boolean moveToRightLane(StreetCell currentCell){
		GridPoint currentLocation = grid.getLocation(currentCell);
		if(currentCell.CanTurnRight){ //then we know that the car is already in the right lane and does not need to do anything
			return false;
		} else { // we need to check to see if lanes to the right of the car are free
			int xStartValue = 0;
			int yStartValue = 0;
			//we want to search the lane to the right of the car's current location
			if(currentHeading.equals(Direction.North)){
				yStartValue = currentLocation.getY();				
				xStartValue = currentLocation.getX()+1;
			} else if (currentHeading.equals(Direction.South)){
				yStartValue = currentLocation.getY();
				xStartValue = currentLocation.getX()-1;
			} else if(currentHeading.equals(Direction.East)){
				yStartValue = currentLocation.getY()-1;
				xStartValue = currentLocation.getX();
			} else if(currentHeading.equals(Direction.West)){
				yStartValue = currentLocation.getY()+1;
				xStartValue = currentLocation.getX();
			}
			StreetCell cellToMoveTo = this.getOpenLane(xStartValue, yStartValue);
			
			if(cellToMoveTo != null){
				this.moveToCell(cellToMoveTo);
				return true;
			} else {
				return false;
			}
		}
	}
	
	//*************************************************************************************
	//attempts to move to the left lane
	protected boolean moveToLeftLane(StreetCell currentCell){
		if(!currentCell.CanTurnRight){//it is already in the left lane (or turn lane) and it does not need to move
			return false; //do nothing
		} else {// we need to check to see if lanes to the left of the car are free
			GridPoint currentLocation = grid.getLocation(this);
			int xStartValue = 0;
			int yStartValue = 0;
			//we want to start searching the lane to the left of the car's current location
			if(currentHeading.equals(Direction.North)){
				yStartValue = currentLocation.getY();				
				xStartValue = currentLocation.getX()-1;
			} else if (currentHeading.equals(Direction.South)){
				yStartValue = currentLocation.getY();
				xStartValue = currentLocation.getX()+1;
			} else if(currentHeading.equals(Direction.East)){
				yStartValue = currentLocation.getY()+1;
				xStartValue = currentLocation.getX();
			} else if(currentHeading.equals(Direction.West)){
				yStartValue = currentLocation.getY()-1;
				xStartValue = currentLocation.getX();
			}
			StreetCell cellToMoveTo = this.getOpenLane(xStartValue, yStartValue);
			if(cellToMoveTo != null){
				this.moveToCell(cellToMoveTo);
				return true;
			} else{
				return false;
			}
		}
	}
	
	//***********************************************************************************
	//attempts to find an open lane
	protected StreetCell getOpenLane(int xStartValue, int yStartValue){
		StreetCell checkedCell;
		StreetCell cellToMoveTo = null;
		int[] unitVelocity = this.getUnitVelocityFromHeading(currentHeading);
		double distanceToCheck = Math.ceil(this.speed); //how far do we want the car to check what lane to move in to
		for(int i=1; (i<=distanceToCheck) && cellToMoveTo==null; i++){
			if(CityBuilder.withinGrid(xStartValue+unitVelocity[0]*i, yStartValue+unitVelocity[1]*i)){
				for(Object o : grid.getObjectsAt(xStartValue+unitVelocity[0]*i, yStartValue+unitVelocity[1]*i)){
					if(o instanceof StreetCell){
						checkedCell = (StreetCell) o;
						if(checkedCell.getHeading().equals(this.currentHeading) && checkedCell.isOpen() && checkedCell.getNextCell()!= null &&checkedCell.getNextCell().isOpen()){
							cellToMoveTo = checkedCell;
						}
					} 
				}
			}
		}
		return cellToMoveTo;
	}
	
	//*******************************************************************************************************
	//executes a right turn
	protected boolean makeRightTurn() {
		StreetCell cellToTurnTo = this.getCellToTurnTo(Math.ceil(this.maxSpeed));
		
		if(cellToTurnTo != null && cellToTurnTo.isOpen()){
			this.location = cellToTurnTo.location;
			this.moveToLocation();
			return true;
		}
		return false;
	}

	//***************************************************************************************************
	//executes a left turn
	protected boolean makeLeftTurn() {
		StreetCell cellToTurnTo = this.getCellToTurnTo(CityBuilder.lanesPerStreet*CityBuilder.laneWidth);
		if(cellToTurnTo != null){
			if(cellToTurnTo.getNextCell().isOpen()){
				double angle = SpatialMath.calcAngleFor2DMovement(space, space.getLocation(this), space.getLocation(cellToTurnTo.getNextCell()));
				space.moveByVector(this, space.getDistance(space.getLocation(this), space.getLocation(cellToTurnTo.getNextCell())), angle,0);
				NdPoint point = space.getLocation(this);
				grid.moveTo(this, (int)point.getX(), (int)point.getY());
				return true;
			}
		}
		return false;
	}

	//*******************************************************************************
	//finds a cell to turn towards
	protected StreetCell getCellToTurnTo(double distanceToCheck) {
		//first check to see if the car is able to turn
		GridPoint currentLocation = grid.getLocation(this);
		int[] unitVelocity = this.getUnitVelocityFromHeading(this.currentHeading);
		StreetCell checkedCell;
		StreetCell cellToTurnTo = null;
		boolean didFindTurn = false;
		boolean isBlocked = false;
		//the number of cars it finds in that grid, if it is checking the same grid it will find itself, but it will need to see if 
		//there are other cars there as well
		int carCount;
		//in this loop we determine if there is a streetcell within reach of the car that matches its future heading, in that case
		//the car will make a turn onto that street cell
		for(int i=0; (i<=distanceToCheck)  && !didFindTurn && !isBlocked; i++){
			carCount = 0;
			if(CityBuilder.withinGrid(currentLocation.getX()+unitVelocity[0]*i, currentLocation.getY()+unitVelocity[1]*i)){
				for(Object o : grid.getObjectsAt(currentLocation.getX()+unitVelocity[0]*i, currentLocation.getY()+unitVelocity[1]*i)){
					if(o instanceof StreetCell){
						checkedCell = (StreetCell) o;
						if(checkedCell.getHeading().equals(this.futureHeading) && !futureHeading.equals(this.currentHeading) /*&& !(checkedCell.CanTurnLeft&&currentCell.CanTurnLeft)*/){
							//we don't turn to the cell if we are already heading in that direction 
							cellToTurnTo = checkedCell;
							didFindTurn = true;
						}
					}
					else if(o instanceof Accident){
						didFindTurn = false;
						cellToTurnTo = null;
						isBlocked = true;
						break;//this particular grid cell is no good
					} else if(o instanceof Car){
						carCount++;
					}
				}
			}
		}
		return cellToTurnTo;
	}
	
	//*****************************************************************************************
	/*
	 * this is for when the car needs to move to get in a position to turn left
	 * @return true if the car was able to move , false otherwise
	 */
	protected boolean moveToTurnLeft(StreetCell currentCell){
		if(currentCell.CanTurnLeft){
			return false; //do nothing
		} else if(currentCell.CanTurnRight){//then the car is in the right lane and it needs to move to the left lane before it can move to the turn lane
			return this.moveToLeftLane(currentCell);
		} else{//we are already in the left lane and we need to move the left turn lane
			if(this.getDistanceToNextLight() <= CityBuilder.lanesOnSide + CityBuilder.laneWidth){
				//we don't want to get into the left turn lane if we are already in the intersection
				return false;
			}
			GridPoint currentLocation = grid.getLocation(currentCell);
			int xStartValue = 0;
			int yStartValue = 0;
			int[] unitVelocity = this.getUnitVelocity();
			if(currentHeading.equals(Direction.North)){
				yStartValue = currentLocation.getY();				
				xStartValue = currentLocation.getX()-1;
			} else if (currentHeading.equals(Direction.South)){
				yStartValue = currentLocation.getY();
				xStartValue = currentLocation.getX()+1;
			} else if(currentHeading.equals(Direction.East)){
				yStartValue = currentLocation.getY()+1;
				xStartValue = currentLocation.getX();
			} else if(currentHeading.equals(Direction.West)){
				yStartValue = currentLocation.getY()-1;
				xStartValue = currentLocation.getX();
			}
			StreetCell checkedCell;
			StreetCell cellToMoveTo = null;
			double distanceToCheck = Math.ceil(speed); //how far do we want the car to check what lane to move in to
			for(int i=1; (i<=distanceToCheck) && cellToMoveTo==null; i++){
				if(CityBuilder.withinGrid(xStartValue+unitVelocity[0]*i, yStartValue+unitVelocity[1]*i)){
					for(Object o : grid.getObjectsAt(xStartValue+unitVelocity[0]*i, yStartValue+unitVelocity[1]*i)){
						if(o instanceof StreetCell){
							checkedCell = (StreetCell) o;
							if(checkedCell.getHeading().equals(this.currentHeading) && checkedCell.CanTurnLeft && checkedCell.isOpen() && checkedCell.getNextCell()!=null && checkedCell.getNextCell().isOpen()){
								cellToMoveTo = checkedCell;
							}
						} else if(o instanceof Car || o instanceof Accident){//don't want to move to StreetCell that already has another car
							cellToMoveTo = null;
							break; //this cell is not good
						}
					}
				}
			}
			if(cellToMoveTo != null){
				this.moveToCell(cellToMoveTo);
				return true;
			} else {
				return false;
			}
		}
	}
	
	//************************************************************************
	//returns the streetCell that the car currently occupies
	protected StreetCell getCurrentCell(GridPoint currentLocation){
		StreetCell currentCell = null;		
		for(Object o : grid.getObjectsAt(currentLocation.getX(),currentLocation.getY())){
			if(o instanceof StreetCell){
				currentCell = (StreetCell) o;
				if(currentCell.getHeading().equals(this.currentHeading)){
					break;
				} else {
					currentCell = null;
				}
			} else if(o instanceof Signal){
			//then we shift above and to the right of the signal (ie move one unit in the current direction and one unit in the future direction)
			int[] currentVelocity = this.getUnitVelocityFromHeading(this.currentHeading);
			int[] futureVelocity = this.getUnitVelocityFromHeading(this.futureHeading);
			double x = currentLocation.getX() + futureVelocity[0] + currentVelocity[0];
			double y = currentLocation.getY() + futureVelocity[1] + currentVelocity[1];
			for (Object o2 : grid.getObjectsAt((int)x,(int)y)){
				if(o2 instanceof StreetCell){
					currentCell = (StreetCell) o2;
					if(currentCell.getHeading().equals(this.futureHeading) || currentCell.getHeading().equals(this.currentHeading)){
						break;
					}
				}
			}
			break;
		}
		}
		return currentCell;
	}
	
	//***********************************************************************
	//moves the car forward
	protected void moveStraight(){
		int[] velocity = getUnitVelocity();
		NdPoint point = space.getLocation(this);
		//check to be sure that the car will be within the grid
		if(CityBuilder.withinGrid(point.getX() + velocity[0]*this.speed, point.getY()+velocity[1]*this.speed)){
			space.moveTo(this, point.getX() + velocity[0]*this.speed, point.getY()+velocity[1]*this.speed);
			grid.moveTo(this, (int)(point.getX() +velocity[0]*this.speed), (int)(point.getY()+velocity[1]*this.speed));
		} else {
			//the car has left the city
			CityBuilder.removeFromContext(this);
			CityBuilder.globalCarCount--;
		}
	}

	
	//*********************************************************************************
	//snaps the car to a given cell
	protected void moveToCell(StreetCell cellToMoveTo){
		if(!cellToMoveTo.getHeading().equals(this.currentHeading)){
			return;
		}
		
		GridPoint pointToMoveTo = cellToMoveTo.getLocation();
		if(CityBuilder.withinGrid(pointToMoveTo.getX(), pointToMoveTo.getY())){
			grid.moveTo(this, pointToMoveTo.getX(),pointToMoveTo.getY());
			space.moveTo(this, pointToMoveTo.getX(),pointToMoveTo.getY());
		}
	}
	
	//*******************************************************************************
	//checks if the destination is within reach
	protected boolean checkForArrival(){
		GridPoint destinationLocation = this.destination.getSpawnLocation().location;
		GridPoint currentLocation = grid.getLocation(this);
		if(grid.getDistance(currentLocation, destinationLocation) <= this.speed){
			return true;
		} else {
			return false;
		}
	}
	
	//*************************************************************************
	
	protected int[] getUnitVelocity(){
		return getUnitVelocityFromHeading(this.currentHeading);
	}
	
	//*************************************************************************
	/*
	 * returns an int vector: [1,0] for east, [0,1] for north [-1,0] for west, and [0,-1] for south
	 */
	protected int[] getUnitVelocityFromHeading(Direction heading){
		int xVelocity = 0;
		int yVelocity = 0;
		if(heading.equals(Direction.North)){
			yVelocity = 1;
		} else if (heading.equals(Direction.South)){
			yVelocity = -1;
		} else if(heading.equals(Direction.East)){
			xVelocity = 1;
		} else if(heading.equals(Direction.West)){
			xVelocity = -1;
		}
		return new int[] {xVelocity, yVelocity};
	}
	
	//******************************************************************
	/*
	 * determines if the car needs to move to the right lane to arrive at its destination
	 */
	protected boolean needToMoveRightToArrive(){
		GridPoint currentLocation = grid.getLocation(this);
		if(currentHeading.equals(Direction.North) || currentHeading.equals(Direction.South)){
			return Math.abs(destination.location.getX() - currentLocation.getX()) <=2;
		} else {
			return Math.abs(destination.location.getY() - currentLocation.getY()) <=2;
		}
	}
	
	//**************************************************************
	/*
	 * determines the distance to the next light based only on the direction that the car is travelling
	 */
	protected double getDistanceToNextLight(){
		GridPoint currentLocation = grid.getLocation(this);
		double distanceToLight = -1;
		if(currentHeading.equals(Direction.North)){
			distanceToLight = nextLight.location.getY() - currentLocation.getY();
		} else if (currentHeading.equals(Direction.South)){
			distanceToLight = currentLocation.getY()- nextLight.location.getY();
		} else if (currentHeading.equals(Direction.East)){
			distanceToLight = (nextLight.location.getX() - currentLocation.getX());
		} else {//west
			distanceToLight = currentLocation.getX() - nextLight.location.getX();
		}
		return distanceToLight;
	}
	
	//***************************************************************************
	
	public Direction getCurrentHeading(){
		return this.currentHeading;
	}

	public double getRed()
	{
		return redColor;
	}
	public double getBlue()
	{
		return blueColor;
	}
	public double getGreen()
	{
		return greenColor;
	}
	
	//******************************************************************************************
	//Output for a dataset
	//returns the number of ticks this car has lived for; to be averaged with other cars
	public long getTicks()
	{
		return tickCount;
	}
	//Output for a dataset
	//returns the current speed of this car; to be averaged with other cars
	public double getSpeed()
	{
		return speed;
	}
	//Output for a dataset
	//returns the number of ticks this car has been watching its current light; to be averaged with other cars
	public int getTicksAtLight()
	{
		return ticksAtLight;
	}
	
	public static long getTotalCars()
	{
		return totalCars;
	}
}//end class
